package com.xbxm.retrofiturlmanager

import android.text.TextUtils.isEmpty
import android.util.Log.getStackTraceString
import androidx.core.util.Preconditions.checkNotNull
import com.orhanobut.logger.LogAdapter
import com.orhanobut.logger.Logger.*
import com.orhanobut.logger.Printer
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.io.StringReader
import java.io.StringWriter
import java.util.*
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerException
import javax.xml.transform.TransformerFactory
import javax.xml.transform.stream.StreamResult
import javax.xml.transform.stream.StreamSource

class MyLoggerPrinter : Printer {

    /**
     * It is used for json pretty print
     */
    private val JSON_INDENT = 2

    /**
     * Provides one-time used tag for the log message
     */
//    private val localTag = ThreadLocal<String>()

    private var localTag: String? = null

    private val logAdapters = ArrayList<LogAdapter>()

    override fun t(tag: String?): Printer {
        if (tag != null) {
            localTag = tag
        }
        return this
    }

    override fun d(message: String, vararg args: Any) {
        log(DEBUG, null, message, *args)
    }

    override fun d(`object`: Any?) {
        log(DEBUG, null, toStr(`object`))
    }

    override fun e(message: String, vararg args: Any) {
        e(null, message, *args)
    }

    override fun e(throwable: Throwable?, message: String, vararg args: Any) {
        log(ERROR, throwable, message, *args)
    }

    override fun w(message: String, vararg args: Any) {
        log(WARN, null, message, *args)
    }

    override fun i(message: String, vararg args: Any) {
        log(INFO, null, message, *args)
    }

    override fun v(message: String, vararg args: Any) {
        log(VERBOSE, null, message, *args)
    }

    override fun wtf(message: String, vararg args: Any) {
        log(ASSERT, null, message, *args)
    }

    override fun json(json: String?) {
        var json = json
        if (json.isNullOrEmpty()) {
            d("Empty/Null json content")
            return
        }
        try {
            json = json!!.trim { it <= ' ' }
            if (json.startsWith("{")) {
                val jsonObject = JSONObject(json)
                val message = jsonObject.toString(JSON_INDENT)
                d(message)
                return
            }
            if (json.startsWith("[")) {
                val jsonArray = JSONArray(json)
                val message = jsonArray.toString(JSON_INDENT)
                d(message)
                return
            }
            e("Invalid Json")
        } catch (e: JSONException) {
            e("Invalid Json")
        }

    }

    override fun xml(xml: String?) {
        if (isEmpty(xml)) {
            d("Empty/Null xml content")
            return
        }
        try {
            val xmlInput = StreamSource(StringReader(xml!!))
            val xmlOutput = StreamResult(StringWriter())
            val transformer = TransformerFactory.newInstance().newTransformer()
            transformer.setOutputProperty(OutputKeys.INDENT, "yes")
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
            transformer.transform(xmlInput, xmlOutput)
            d(xmlOutput.writer.toString().replaceFirst(">".toRegex(), ">\n"))
        } catch (e: TransformerException) {
            e("Invalid xml")
        }

    }

    @Synchronized
    override fun log(priority: Int,
                     tag: String?,
                     message: String?,
                     throwable: Throwable?) {
        var message = message
        if (throwable != null && message != null) {
            message += " : " + getStackTraceString(throwable)
        }
        if (throwable != null && message == null) {
            message = getStackTraceString(throwable)
        }
        if (isEmpty(message)) {
            message = "Empty/NULL log message"
        }

        for (adapter in logAdapters) {
            if (adapter.isLoggable(priority, tag)) {
                adapter.log(priority, tag, message!!)
            }
        }
    }

    override fun clearLogAdapters() {
        logAdapters.clear()
    }

    override fun addAdapter(adapter: LogAdapter) {
        logAdapters.add(checkNotNull(adapter))
    }

    /**
     * This method is synchronized in order to avoid messy of logs' order.
     */
    @Synchronized
    private fun log(priority: Int,
                    throwable: Throwable?,
                    msg: String,
                    vararg args: Any) {
        checkNotNull(msg)

        val tag = getTag()
        val message = createMessage(msg, *args)
        log(priority, tag, message, throwable)
    }

    /**
     * @return the appropriate tag based on local or global
     */
    private fun getTag(): String? {
//        val tag = localTag.get()
//        if (tag != null) {
//            localTag.remove()
//            return tag
//        }
        return localTag
    }

    private fun createMessage(message: String, vararg args: Any): String {
        return if (args == null || args.size == 0) message else String.format(message, *args)
    }

    fun toStr(`object`: Any?): String {
        if (`object` == null) {
            return "null"
        }
        if (!`object`.javaClass.isArray) {
            return `object`.toString()
        }
        if (`object` is BooleanArray) {
            return Arrays.toString(`object` as BooleanArray?)
        }
        if (`object` is ByteArray) {
            return Arrays.toString(`object` as ByteArray?)
        }
        if (`object` is CharArray) {
            return Arrays.toString(`object` as CharArray?)
        }
        if (`object` is ShortArray) {
            return Arrays.toString(`object` as ShortArray?)
        }
        if (`object` is IntArray) {
            return Arrays.toString(`object` as IntArray?)
        }
        if (`object` is LongArray) {
            return Arrays.toString(`object` as LongArray?)
        }
        if (`object` is FloatArray) {
            return Arrays.toString(`object` as FloatArray?)
        }
        if (`object` is DoubleArray) {
            return Arrays.toString(`object` as DoubleArray?)
        }
        return if (`object` is Array<*>) {
            Arrays.deepToString(`object` as Array<Any>?)
        } else "Couldn't find a correct type for the object"
    }
}